package com.googlecode.autopp.conf;

import java.lang.reflect.Constructor;

import com.googlecode.autopp.ObjectContext;

/**
 * Enum with implemented nesting strategies for creating objects.
 */
public enum NestingStrategyImpl implements NestingStrategy {

    /**
     * Creates new object only for class which were given, other objects are
     * nulls.
     */
    NULL {
        @Override
        public <T> T create(ObjectContext<T> contex) {
            if (contex.getStep() == 0) {
                return createObject(contex.getClazz());
            }
            return null;
        }
    },

    /**
     * Creates new object for class which were given, for others tries to
     * initialize them, but without setting any properties.
     */
    NEW_OBJECT {

        @Override
        public <T> T create(ObjectContext<T> contex) {
            if (contex.getStep() == 0 || contex.getStep() == 1) {
                return createObject(contex.getClazz());
            }
            return null;
        }
    },

    /**
     * Creates new object for each class. May cause out of memory, so this
     * strategy should be used with caution!
     */
    NEW_FOR_ALL {
        @Override
        public <T> T create(ObjectContext<T> contex) {
            return createObject(contex.getClazz());
        }
    };

    /**
     * Creates new object with given type using default constructor without any
     * arguments.
     * 
     * @param <T>
     *            Type of created object.
     * @param clazz
     *            Type of created object.
     * 
     * @return Object created with default constructor.
     */
    private static <T> T createObject(Class<T> clazz) {
        if (clazz.isInterface() || clazz.isEnum()) {
            throw new IllegalArgumentException("Cannot create object of enum or interface type.");
        }
        try {
            // WARNING! There may be many different generated constructors,
            // especially in inner non-static classes!
            // for non-static inner classes see 
            // http://www.jroller.com/tomdz/entry/reflection_inner_classes
            Constructor<T> c = clazz.getConstructor();
            return c.newInstance();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Cannot populate object of type [" + clazz.getSimpleName() + "] without public non-argument constructor.", e);
        } catch (Exception e) {
            throw new UnsupportedOperationException("Problem occured in environment.", e);
        }
    }
}
